#include <QDate> // for QDate
#include <QDateTime> // for QDateTime
#include <QFile> // for QFile
+#include <QHash> // for QHash
#include <QIODevice> // for operator|, QIODevice, QIODevice::Text, QIODevice::WriteOnly
#include <QList> // for QList
#include <QString> // for QString, QStringLiteral, operator+, operator!=
#define MYNAME "kml"
+const QVector<KmlFormat::mt_field_t> KmlFormat::mt_fields_def = {
+ { wp_field::igc_enl, "igc_enl", "Engine Noise", "double" },
+ { wp_field::igc_tas, "igc_tas", "True Airspd", "double" },
+ { wp_field::igc_oat, "igc_oat", "Otsd Air Temp", "double" },
+ { wp_field::igc_vat, "igc_vat", "Ttl Enrg Vario", "double" },
+ { wp_field::igc_gsp, "igc_gsp", "Ground Speed", "double" },
+ { wp_field::igc_fxa, "igc_fxa", "Fix Accuracy", "double" },
+ { wp_field::igc_gfo, "igc_gfo", "G Force?", "double" },
+ { wp_field::igc_acz, "igc_acz", "Z Accel", "double" },
+ { wp_field::igc_siu, "igc_siu", "# Of Sats", "double" },
+ { wp_field::igc_trt, "igc_trt", "True Track", "double" },
+ { wp_field::cadence, "cadence", "Cadence", "int" },
+ { wp_field::depth, "depth", "Depth", "float" },
+ { wp_field::heartrate, "heartrate", "Heart Rate", "int" },
+ { wp_field::temperature, "temperature", "Temperature", "float" },
+ { wp_field::power, "power", "Power", "float" },
+ { wp_field::sat, "satellites", "Satellites", "int" },
+};
+
void KmlFormat::kml_init_color_sequencer(unsigned int steps_per_rev)
{
if (rotate_colors) {
} else if ((color_seq >= (2*kml_color_limit)) && (color_seq < (3*kml_color_limit))) {
kml_color_sequencer.color.bbggrr = kml_bgr_to_color(color_seq-2*kml_color_limit, kml_color_limit, 0);
} else if ((color_seq >= (3*kml_color_limit)) && (color_seq < (4*kml_color_limit))) {
- kml_color_sequencer.color.bbggrr = kml_bgr_to_color(kml_color_limit, 4*kml_color_limit-color_seq ,0);
+ kml_color_sequencer.color.bbggrr = kml_bgr_to_color(kml_color_limit, 4*kml_color_limit-color_seq,0);
} else if ((color_seq >= (4*kml_color_limit)) && (color_seq < (5*kml_color_limit))) {
kml_color_sequencer.color.bbggrr = kml_bgr_to_color(kml_color_limit, 0, color_seq-4*kml_color_limit);
} else if ((color_seq >= (5*kml_color_limit)) && (color_seq < (6*kml_color_limit))) {
QFile::remove(posnfilename);
QFile::rename(posnfilenametmp, posnfilename);
}
+
+ kml_track_traits.reset();
+ kml_track_traits_hash.clear();
}
void KmlFormat::wr_position_deinit()
// kml_wr_deinit();
posnfilename.clear();
posnfilenametmp.clear();
+
+ kml_track_traits.reset();
+ kml_track_traits_hash.clear();
}
*/
void KmlFormat::kml_mt_simple_array(const route_head* header,
- const char* name,
+ const QString& name,
wp_field member) const
{
writer->writeStartElement(QStringLiteral("gx:SimpleArrayData"));
writer->writeAttribute(QStringLiteral("name"), name);
if (global_opts.debug_level >= 3) {
- printf(MYNAME ": New KML SimpleArray: %s\n", name);
+ printf(MYNAME ": New KML SimpleArray: %s\n", qPrintable(name));
}
foreach (const Waypoint* wpt, header->waypoint_list) {
const auto* fs_igc = reinterpret_cast<igc_fsdata*>(wpt->fs.FsChainFind(kFsIGC));
break;
case wp_field::sat:
writer->writeTextElement(QStringLiteral("gx:value"), wpt->sat >= 0?
- QString::number(wpt->sat) : QString());
+ QString::number(wpt->sat) : QString());
break;
case wp_field::igc_enl:
case wp_field::igc_tas:
if (fs_igc && fs_igc->get_value(member).has_value()) {
double value = fs_igc->get_value(member).value();
if (global_opts.debug_level >= 6) {
- printf(MYNAME ": Writing KML SimpleArray data: %s of %f\n", name, value);
+ printf(MYNAME ": Writing KML SimpleArray data: %s of %f\n", qPrintable(name), value);
}
writer->writeTextElement(QStringLiteral("gx:value"), QString::number(value));
- // No igc_fsdata present, but we still need to write out the SimpleArray.
- // This can happen when merging tracks with different sets of IGC extensions.
+ // No igc_fsdata present, but we still need to write out the SimpleArray.
+ // This can happen when merging tracks with different sets of IGC extensions.
} else {
if (global_opts.debug_level >= 7) {
- printf(MYNAME ": Writing empty KML SimpleArray data for %s\n", name);
+ printf(MYNAME ": Writing empty KML SimpleArray data for %s\n", qPrintable(name));
}
writer->writeTextElement(QStringLiteral("gx:value"), QString());
}
}
-void KmlFormat::kml_mt_hdr(const route_head* header)
+void KmlFormat::kml_accumulate_track_traits(const route_head* rte)
{
- bool has_cadence = false;
- bool has_depth = false;
- bool has_heartrate = false;
- bool has_temperature = false;
- bool has_power = false;
- bool has_sat = false;
- bool has_igc_exts = false;
- bool has_igc_enl = false;
- bool has_igc_tas = false;
- bool has_igc_oat = false;
- bool has_igc_vat = false;
- bool has_igc_gsp = false;
- bool has_igc_fxa = false;
- bool has_igc_gfo = false;
- bool has_igc_acz = false;
- bool has_igc_siu = false; // Not very useful to graph
- bool has_igc_trt = false; // Not very useful to graph
-
- // This logic is kind of inside-out for GPSBabel. If a track doesn't
- // have enough interesting timestamps, just write it as a LineString.
- if (!track_has_time(header)) {
- write_as_linestring(header);
- return;
- }
-
- writer->writeStartElement(QStringLiteral("Placemark"));
- writer->writeOptionalTextElement(QStringLiteral("name"), header->rte_name);
- writer->writeTextElement(QStringLiteral("styleUrl"), QStringLiteral("#multiTrack"));
- writer->writeStartElement(QStringLiteral("gx:Track"));
- kml_output_positioning(false);
-
- foreach (const Waypoint* tpt, header->waypoint_list) {
-
- if (tpt->GetCreationTime().isValid()) {
- QString time_string = tpt->CreationTimeXML();
- writer->writeOptionalTextElement(QStringLiteral("when"), time_string);
- } else {
- writer->writeStartElement(QStringLiteral("when"));
- writer->writeEndElement(); // Close when tag
- }
- }
-
- // TODO: How to handle clamped, floating, extruded, etc.?
- foreach (const Waypoint* tpt, header->waypoint_list) {
+ track_trait_t track_traits;
+ foreach (const Waypoint* tpt, rte->waypoint_list) {
const auto* fs_igc = reinterpret_cast<igc_fsdata*>(tpt->fs.FsChainFind(kFsIGC));
- if (kml_altitude_known(tpt)) {
- writer->writeTextElement(QStringLiteral("gx:coord"),
- QString::number(tpt->longitude, 'f', precision) + QString(" ") +
- QString::number(tpt->latitude, 'f', precision) + QString(" ") +
- QString::number(tpt->altitude, 'f', 2)
- );
- } else {
- writer->writeTextElement(QStringLiteral("gx:coord"),
- QString::number(tpt->longitude, 'f', precision) + QString(" ") +
- QString::number(tpt->latitude, 'f', precision)
- );
- }
// Capture interesting traits to see if we need to do an ExtendedData
// section later.
if (tpt->cadence) {
- has_cadence = true;
+ track_traits[static_cast<int>(wp_field::cadence)] = true;
}
if (tpt->depth_has_value()) {
- has_depth = true;
+ track_traits[static_cast<int>(wp_field::depth)] = true;
}
if (tpt->heartrate) {
- has_heartrate = true;
+ track_traits[static_cast<int>(wp_field::heartrate)] = true;
}
if (tpt->temperature_has_value()) {
- has_temperature = true;
+ track_traits[static_cast<int>(wp_field::temperature)] = true;
}
if (tpt->power) {
- has_power = true;
+ track_traits[static_cast<int>(wp_field::power)] = true;
}
// # of satellites can legitimately be zero, so -1 means no data in this case
if (tpt->sat >= 0) {
- has_sat = true;
+ track_traits[static_cast<int>(wp_field::sat)] = true;
}
if (fs_igc) {
- has_igc_exts = true;
if (fs_igc->enl.has_value()) {
- has_igc_enl = true;
+ track_traits[static_cast<int>(wp_field::igc_enl)] = true;
}
if (fs_igc->tas.has_value()) {
- has_igc_tas = true;
+ track_traits[static_cast<int>(wp_field::igc_tas)] = true;
}
if (fs_igc->oat.has_value()) {
- has_igc_oat = true;
+ track_traits[static_cast<int>(wp_field::igc_oat)] = true;
}
if (fs_igc->vat.has_value()) {
- has_igc_vat = true;
+ track_traits[static_cast<int>(wp_field::igc_vat)] = true;
}
if (fs_igc->gsp.has_value()) {
- has_igc_gsp = true;
+ track_traits[static_cast<int>(wp_field::igc_gsp)] = true;
}
if (fs_igc->fxa.has_value()) {
- has_igc_fxa = true;
+ track_traits[static_cast<int>(wp_field::igc_fxa)] = true;
}
if (fs_igc->gfo.has_value()) {
- has_igc_gfo = true;
+ track_traits[static_cast<int>(wp_field::igc_gfo)] = true;
}
if (fs_igc->acz.has_value()) {
- has_igc_acz = true;
+ track_traits[static_cast<int>(wp_field::igc_acz)] = true;
}
if constexpr(kIncludeIGCSIU) {
if (fs_igc->siu.has_value()) {
- has_igc_siu = true;
+ track_traits[static_cast<int>(wp_field::igc_siu)] = true;
}
}
if constexpr(kIncludeIGCTRT) {
if (fs_igc->trt.has_value()) {
- has_igc_trt = true;
+ track_traits[static_cast<int>(wp_field::igc_trt)] = true;
}
}
}
}
- // This gets unwieldly if we check each individual igc extension,
- // hence the has_igc_exts flag.
- if (has_cadence || has_depth || has_heartrate || has_temperature ||
- has_power || has_sat || has_igc_exts) {
- bool include_kmt_sats = true;
- bool include_kmt_temperature = true;
- writer->writeStartElement(QStringLiteral("ExtendedData"));
- writer->writeStartElement(QStringLiteral("SchemaData"));
- writer->writeAttribute(QStringLiteral("schemaUrl"), QStringLiteral("#schema"));
+ // For dual source fields give priority to igc.
+ if (track_traits[static_cast<int>(wp_field::igc_oat)]) {
+ track_traits[static_cast<int>(wp_field::temperature)] = false;
+ }
+ if (track_traits[static_cast<int>(wp_field::igc_siu)]) {
+ track_traits[static_cast<int>(wp_field::sat)] = false;
+ }
- // Perhaps not the /best/ way to do this, but this if ladder
- // should only be evaluated once.
- if (has_igc_exts) {
- if (has_igc_enl) {
- kml_mt_simple_array(header, kmt_igc_enl, wp_field::igc_enl);
- }
- if (has_igc_tas) {
- kml_mt_simple_array(header, kmt_igc_tas, wp_field::igc_tas);
- }
- if (has_igc_oat) {
- kml_mt_simple_array(header, kmt_igc_oat, wp_field::igc_oat);
- include_kmt_temperature = false;
- }
- if (has_igc_vat) {
- kml_mt_simple_array(header, kmt_igc_vat, wp_field::igc_vat);
- }
- if (has_igc_gsp) {
- kml_mt_simple_array(header, kmt_igc_gsp, wp_field::igc_gsp);
- }
- if (has_igc_fxa) {
- kml_mt_simple_array(header, kmt_igc_fxa, wp_field::igc_fxa);
- }
- if (has_igc_gfo) {
- kml_mt_simple_array(header, kmt_igc_gfo, wp_field::igc_gfo);
- }
- if (has_igc_acz) {
- kml_mt_simple_array(header, kmt_igc_acz, wp_field::igc_acz);
- }
- if constexpr(kIncludeIGCSIU) {
- if (has_igc_siu) {
- kml_mt_simple_array(header, kmt_igc_siu, wp_field::igc_siu);
- include_kmt_sats = false;
- }
- }
- if constexpr(kIncludeIGCTRT) {
- if (has_igc_trt) {
- kml_mt_simple_array(header, kmt_igc_trt, wp_field::igc_trt);
- }
- }
- }
+ // When doing real time positioning we may already have been here.
+ // If so, replace the previous value corresponding to the key.
+ // If not, insert a new key value pair.
+ kml_track_traits_hash.insert(rte, track_traits);
+ kml_track_traits |= track_traits;
+}
- if (has_cadence) {
- kml_mt_simple_array(header, kmt_cadence, wp_field::cadence);
- }
+void KmlFormat::kml_mt_hdr(const route_head* header)
+{
+ // This logic is kind of inside-out for GPSBabel. If a track doesn't
+ // have enough interesting timestamps, just write it as a LineString.
+ if (!track_has_time(header)) {
+ write_as_linestring(header);
+ return;
+ }
- if (has_depth) {
- kml_mt_simple_array(header, kmt_depth, wp_field::depth);
- }
+ writer->writeStartElement(QStringLiteral("Placemark"));
+ writer->writeOptionalTextElement(QStringLiteral("name"), header->rte_name);
+ writer->writeTextElement(QStringLiteral("styleUrl"), QStringLiteral("#multiTrack"));
+ writer->writeStartElement(QStringLiteral("gx:Track"));
+ kml_output_positioning(false);
- if (has_heartrate) {
- kml_mt_simple_array(header, kmt_heartrate, wp_field::heartrate);
- }
+ foreach (const Waypoint* tpt, header->waypoint_list) {
- if (has_temperature && include_kmt_temperature) {
- kml_mt_simple_array(header, kmt_temperature, wp_field::temperature);
+ if (tpt->GetCreationTime().isValid()) {
+ QString time_string = tpt->CreationTimeXML();
+ writer->writeOptionalTextElement(QStringLiteral("when"), time_string);
+ } else {
+ writer->writeStartElement(QStringLiteral("when"));
+ writer->writeEndElement(); // Close when tag
}
+ }
- if (has_power) {
- kml_mt_simple_array(header, kmt_power, wp_field::power);
+ // TODO: How to handle clamped, floating, extruded, etc.?
+ foreach (const Waypoint* tpt, header->waypoint_list) {
+
+ if (kml_altitude_known(tpt)) {
+ writer->writeTextElement(QStringLiteral("gx:coord"),
+ QString::number(tpt->longitude, 'f', precision) + QString(" ") +
+ QString::number(tpt->latitude, 'f', precision) + QString(" ") +
+ QString::number(tpt->altitude, 'f', 2)
+ );
+ } else {
+ writer->writeTextElement(QStringLiteral("gx:coord"),
+ QString::number(tpt->longitude, 'f', precision) + QString(" ") +
+ QString::number(tpt->latitude, 'f', precision)
+ );
}
+ }
+
+
+ auto track_traits = kml_track_traits_hash.value(header);
+ if (track_traits.any()) {
+ writer->writeStartElement(QStringLiteral("ExtendedData"));
+ writer->writeStartElement(QStringLiteral("SchemaData"));
+ writer->writeAttribute(QStringLiteral("schemaUrl"), QStringLiteral("#schema"));
- if (has_sat && include_kmt_sats) {
- kml_mt_simple_array(header, kmt_sat, wp_field::sat);
+ for (const auto& flddef : mt_fields_def) {
+ if (track_traits[static_cast<int>(flddef.id)]) {
+ kml_mt_simple_array(header, flddef.name, flddef.id);
+ }
}
writer->writeEndElement(); // Close SchemaData tag
}
}
-
void KmlFormat::kml_mt_tlr(const route_head* header) const
{
if (track_has_time(header)) {
}
-void KmlFormat::kml_mt_array_schema(const char* field_name, const char* display_name,
- const char* type) const
+void KmlFormat::kml_mt_array_schema(const QString& field_name, const QString& display_name,
+ const QString& type) const
{
writer->writeStartElement(QStringLiteral("gx:SimpleArrayField"));
writer->writeAttribute(QStringLiteral("name"), field_name);
void KmlFormat::write()
{
- const global_trait* traits = get_traits();
-
// Parse options
export_lines = (0 == strcmp("1", opt_export_lines));
export_points = (0 == strcmp("1", opt_export_points));
writer->writeEndElement(); // Close Style tag
}
- if (traits->trait_geocaches) {
+ bool has_geocaches = false;
+ auto kml_accumulate_wpt_traits_lambda = [&has_geocaches](const Waypoint* wpt)->void {
+ has_geocaches |= (wpt->gc_data->diff && wpt->gc_data->terr);
+ };
+ waypt_disp_all(kml_accumulate_wpt_traits_lambda);
+
+ if (has_geocaches) {
kml_gc_make_balloonstyle();
}
- if (traits->trait_heartrate ||
- traits->trait_cadence ||
- traits->trait_power ||
- traits->trait_temperature ||
- traits->trait_depth ||
- traits->trait_sat) {
- writer->writeStartElement(QStringLiteral("Schema"));
- writer->writeAttribute(QStringLiteral("id"), QStringLiteral("schema"));
+ if (export_track) {
+ kml_track_traits.reset();
+ kml_track_traits_hash.clear();
+ auto kml_accumulate_track_traits_lambda = [this](const route_head* rte)->void {
+ kml_accumulate_track_traits(rte);
+ };
+ track_disp_all(kml_accumulate_track_traits_lambda, nullptr, nullptr);
- if (traits->trait_heartrate) {
- kml_mt_array_schema(kmt_heartrate, "Heart Rate", "int");
- }
- if (traits->trait_cadence) {
- kml_mt_array_schema(kmt_cadence, "Cadence", "int");
- }
- if (traits->trait_power) {
- kml_mt_array_schema(kmt_power, "Power", "float");
- }
- if (traits->trait_temperature) {
- kml_mt_array_schema(kmt_temperature, "Temperature", "float");
- }
- if (traits->trait_depth) {
- kml_mt_array_schema(kmt_depth, "Depth", "float");
- }
- if (traits->trait_sat) {
- kml_mt_array_schema(kmt_sat, "Satellites", "int");
+ if (kml_track_traits.any()) {
+ writer->writeStartElement(QStringLiteral("Schema"));
+ writer->writeAttribute(QStringLiteral("id"), QStringLiteral("schema"));
+
+ for (const auto& flddef : mt_fields_def) {
+ if (kml_track_traits[static_cast<int>(flddef.id)]) {
+ kml_mt_array_schema(flddef.name, flddef.displayName, flddef.type);
+ }
+ }
+ writer->writeEndElement(); // Close Schema tag
}
- writer->writeEndElement(); // Close Schema tag
}
if (waypt_count()) {